﻿namespace NTLib.Pipe.UTest.Containers
{
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using NTLib.Core.Pool;
    using NTLib.Core.Tasks;
    using NTLib.Pipe.Containers;
    using NTLib.Pipe.Descriptors;
    using NTLib.Pipe.Exceptions;
    using NTLib.Pipe.Targets;
    using NTLib.Pipe.UTest.TestData;
    using System;
    using System.Diagnostics;
    using System.Threading;
    using System.Threading.Tasks;

    public class PipeSourceUTest<T> : Test.Resources.BaseTestClass
        where T : IPipeSource<DataStruct>, IPoolItem, new()
    {
        #region Methods

        /// <summary>
        /// Test if the Pipe source is well initialized with the default values
        /// </summary>
        public void PipeSource_CreateAndDispose()
        {
            using (T source = new T())
            {
                AssertFuncException<PipeNotInitializedException, bool>(() => source.CanPopItem);
                AssertFuncException<PipeNotInitializedException, bool>(() => source.CanPushItem);

                Assert.AreNotEqual(Guid.Empty, source.Id);
                Assert.AreNotEqual(default(Guid), source.Id);

                Assert.AreEqual(PipeType.None, source.PipeType);
                Assert.AreEqual(PipeStatus.NotInitialize, source.Status);

                AssertFuncException<PipeNotInitializedException, ITargetDescriptor>(() => source.Target);
            }
        }

        /// <summary>
        /// Test the creation of the pipe and all the exception 
        /// that should be raised when some functionalities are called whiout have the pipe
        /// initialized correctly.
        /// </summary>
        public void PipeSource_CreationAndInit(ITargetDescriptor init)
        {
            T pipe = new T();
            Assert.IsNotNull(pipe);
            Assert.AreNotEqual(pipe.Id, Guid.Empty);

            pipe.InitializeAsync(init, PipeType.TwoWay).Wait();
            Assert.AreEqual(pipe.CanPopItem, true);
            Assert.AreEqual(pipe.CanPushItem, true);

            try
            {
                pipe.InitializeAsync(init, PipeType.OneWay).Wait();
                Assert.Fail("Mustn't mot be possible to initialize twice the same instance");
            }
            catch (AggregateException ex)
            {
                Assert.IsNotNull(ex.InnerException);
                Assert.AreEqual(ex.InnerExceptions.Count, 1);
                PipeAlreadyInitializedException exPipe = ex.InnerException as PipeAlreadyInitializedException;
                Assert.IsNotNull(exPipe, "We attend to received a PipeAlreadyInitialiedException");
            }
            catch
            {
                Assert.Fail("Expecting PipeAlreadyInitializedException exception");
            }

            Assert.AreEqual(pipe.CanPopItem, true);
            Assert.AreEqual(pipe.CanPushItem, true);


            using (pipe = new T())
            {
                try
                {
                    bool value = pipe.CanPopItem;
                    Assert.Fail("Mustn't mot be possible to initialize twice the same instance");
                }
                catch (PipeNotInitializedException)
                {
                }
                catch
                {
                    Assert.Fail("Expecting PipeNotInitializedException exception");
                }

                try
                {
                    bool value = pipe.CanPushItem;
                    Assert.Fail("Mustn't mot be possible to initialize twice the same instance");
                }
                catch (PipeNotInitializedException)
                {
                }
                catch
                {
                    Assert.Fail("Expecting PipeNotInitializedException exception");
                }

                //try
                //{
                //    TaskCompletionSource<object> waiter = new TaskCompletionSource<object>();
                //    pipe.PushItemAsync<bool>(null, waiter).Wait();
                //    Assert.Fail("Mustn't mot be possible to initialize twice the same instance");
                //}
                //catch (AggregateException ex)
                //{
                //    Assert.IsNotNull(ex.InnerException);
                //    Assert.AreEqual(ex.InnerExceptions.Count, 1);
                //    PipeNotInitializedException exPipe = ex.InnerException as PipeNotInitializedException;
                //    Assert.IsNotNull(exPipe, "We attend to received a PipeNotInitializedException");
                //}
                //catch
                //{
                //    Assert.Fail("Expecting PipeNotInitializedException exception");
                //}


                try
                {
                    pipe.DequeueItemAsync().Wait();
                    Assert.Fail("Mustn't mot be possible to initialize twice the same instance");
                }
                catch (AggregateException ex)
                {
                    Assert.IsNotNull(ex.InnerException);
                    Assert.AreEqual(ex.InnerExceptions.Count, 1);
                    PipeNotInitializedException exPipe = ex.InnerException as PipeNotInitializedException;
                    Assert.IsNotNull(exPipe, "We attend to received a PipeNotInitializedException");
                }
                catch
                {
                    Assert.Fail("Expecting PipeNotInitializedException exception");
                }


                try
                {
                    pipe.SendItemAsync(null).Wait();
                    Assert.Fail("Mustn't mot be possible to initialize twice the same instance");
                }
                catch (AggregateException ex)
                {
                    Assert.IsNotNull(ex.InnerException);
                    Assert.AreEqual(ex.InnerExceptions.Count, 1);
                    PipeNotInitializedException exPipe = ex.InnerException as PipeNotInitializedException;
                    Assert.IsNotNull(exPipe, "We attend to received a PipeNotInitializedException");
                }
                catch
                {
                    Assert.Fail("Expecting PipeNotInitializedException exception");
                }

                Assert.AreNotEqual(pipe.Id, Guid.Empty);
            }
        }

        /// <summary>
        /// Test if the Pipe source is well initialized with the default values coming from a pool system
        /// </summary>
        public void PipeSource_CreateFromPoolAndDispose()
        {
            TypePool pool = new TypePool();
            T refItem = default(T);

            using (T source = pool.Get<T>())
            {
                refItem = source;
                AssertFuncException<PipeNotInitializedException, bool>(() => source.CanPopItem);
                AssertFuncException<PipeNotInitializedException, bool>(() => source.CanPushItem);

                Assert.AreNotEqual(Guid.Empty, source.Id);
                Assert.AreNotEqual(default(Guid), source.Id);

                Assert.AreEqual(PipeType.None, source.PipeType);
                Assert.AreEqual(PipeStatus.NotInitialize, source.Status);

                AssertFuncException<PipeNotInitializedException, ITargetDescriptor>(() => source.Target);
            }
            using (T source = pool.Get<T>())
            {
                Assert.ReferenceEquals(refItem, source);

                refItem = source;
                AssertFuncException<PipeNotInitializedException, bool>(() => source.CanPopItem);
                AssertFuncException<PipeNotInitializedException, bool>(() => source.CanPushItem);

                Assert.AreNotEqual(Guid.Empty, source.Id);
                Assert.AreNotEqual(default(Guid), source.Id);

                Assert.AreEqual(PipeType.None, source.PipeType);
                Assert.AreEqual(PipeStatus.NotInitialize, source.Status);

                AssertFuncException<PipeNotInitializedException, ITargetDescriptor>(() => source.Target);
            }
        }

        /// <summary>
        /// Test the creation of the pipe and all the exception 
        /// that should be raised when some functionalities are called whiout have the pipe
        /// initialized correctly.
        /// </summary>
        public void PipeSource_CreationFromPoolAndInit(ITargetDescriptor init)
        {
            TypePool pool = new TypePool();
            T refItem = default(T);

            using (T pipe = pool.Get<T>())
            {
                refItem = pipe;
                Assert.IsNotNull(pipe);
                Assert.AreNotEqual(pipe.Id, Guid.Empty);

                pipe.InitializeAsync(init, PipeType.TwoWay).Wait();
                Assert.AreEqual(pipe.CanPopItem, true);
                Assert.AreEqual(pipe.CanPushItem, true);

                try
                {
                    pipe.InitializeAsync(init, PipeType.OneWay).Wait();
                    Assert.Fail("Mustn't mot be possible to initialize twice the same instance");
                }
                catch (AggregateException ex)
                {
                    Assert.IsNotNull(ex.InnerException);
                    Assert.AreEqual(ex.InnerExceptions.Count, 1);
                    PipeAlreadyInitializedException exPipe = ex.InnerException as PipeAlreadyInitializedException;
                    Assert.IsNotNull(exPipe, "We attend to received a PipeAlreadyInitialiedException");
                }
                catch
                {
                    Assert.Fail("Expecting PipeAlreadyInitializedException exception");
                }

                Assert.AreEqual(pipe.CanPopItem, true);
                Assert.AreEqual(pipe.CanPushItem, true);
            }

            using (T pipe = pool.Get<T>())
            {
                Assert.ReferenceEquals(refItem, pipe);
                try
                {
                    bool value = pipe.CanPopItem;
                    Assert.Fail("Mustn't mot be possible to initialize twice the same instance");
                }
                catch (PipeNotInitializedException)
                {
                }
                catch
                {
                    Assert.Fail("Expecting PipeNotInitializedException exception");
                }

                try
                {
                    bool value = pipe.CanPushItem;
                    Assert.Fail("Mustn't mot be possible to initialize twice the same instance");
                }
                catch (PipeNotInitializedException)
                {
                }
                catch
                {
                    Assert.Fail("Expecting PipeNotInitializedException exception");
                }

                //try
                //{
                //    TaskCompletionSource<object> waiter = new TaskCompletionSource<object>();
                //    pipe.PushItemAsync<bool>(null, waiter).Wait();
                //    Assert.Fail("Mustn't mot be possible to initialize twice the same instance");
                //}
                //catch (AggregateException ex)
                //{
                //    Assert.IsNotNull(ex.InnerException);
                //    Assert.AreEqual(ex.InnerExceptions.Count, 1);
                //    PipeNotInitializedException exPipe = ex.InnerException as PipeNotInitializedException;
                //    Assert.IsNotNull(exPipe, "We attend to received a PipeNotInitializedException");
                //}
                //catch
                //{
                //    Assert.Fail("Expecting PipeNotInitializedException exception");
                //}


                try
                {
                    pipe.DequeueItemAsync().Wait();
                    Assert.Fail("Mustn't mot be possible to initialize twice the same instance");
                }
                catch (AggregateException ex)
                {
                    Assert.IsNotNull(ex.InnerException);
                    Assert.AreEqual(ex.InnerExceptions.Count, 1);
                    PipeNotInitializedException exPipe = ex.InnerException as PipeNotInitializedException;
                    Assert.IsNotNull(exPipe, "We attend to received a PipeNotInitializedException");
                }
                catch
                {
                    Assert.Fail("Expecting PipeNotInitializedException exception");
                }


                try
                {
                    pipe.SendItemAsync(null).Wait();
                    Assert.Fail("Mustn't mot be possible to initialize twice the same instance");
                }
                catch (AggregateException ex)
                {
                    Assert.IsNotNull(ex.InnerException);
                    Assert.AreEqual(ex.InnerExceptions.Count, 1);
                    PipeNotInitializedException exPipe = ex.InnerException as PipeNotInitializedException;
                    Assert.IsNotNull(exPipe, "We attend to received a PipeNotInitializedException");
                }
                catch
                {
                    Assert.Fail("Expecting PipeNotInitializedException exception");
                }

                Assert.AreNotEqual(pipe.Id, Guid.Empty);
            }

        }


        /// <summary>
        /// Test that the pipe source used with a pool environment will correctly refresh is status.
        /// </summary>
        public void PipeSource_PoolCreationAndStatus()
        {
            TypePool pool = new TypePool();
            int itemCode = 0;
            Guid pipeId = Guid.Empty;

            var pipe = pool.Get<T>();
            using (pipe)
            {
                itemCode = pipe.GetHashCode();
                Assert.AreEqual(pipe.Status, PipeStatus.NotInitialize);
                pipe.InitializeAsync(LocalTargetDescription.Instance, PipeType.TwoWay).Wait();
                pipeId = pipe.Id;
            }
            Assert.AreEqual(pipe.Status, PipeStatus.Closed);

            using (var pipe2 = pool.Get<MemoryPipeSource<DataStruct>>())
            {
                Assert.AreEqual(pipe2.Status, PipeStatus.NotInitialize);

                Assert.AreEqual(pipe2.GetHashCode(), itemCode);
                pipe2.InitializeAsync(LocalTargetDescription.Instance, PipeType.OneWay).Wait();
                Assert.AreEqual(pipe2.Status, PipeStatus.Ready);
                Assert.AreNotEqual(pipe2.Id, pipeId);
            }
        }

        ///// <summary>
        ///// Will test to push and pop and information into the same pipe source.
        ///// </summary>
        //public void PipeSource_PushAndPop(ITargetDescriptor init)
        //{
        //    using (T pipe = new T())
        //    {
        //        pipe.InitializeAsync(init, PipeType.TwoWay).Wait();

        //        string key = Guid.NewGuid().ToString();
        //        DataStruct item = new DataStruct();
        //        item.Data = key;

        //        CancellationTokenSource tokenSource = new CancellationTokenSource();
        //        CancellationTokenSource tokenSourcePop = new CancellationTokenSource();

        //        TaskCompletionSource<object> waiter = new TaskCompletionSource<object>();

        //        tokenSource.CancelAfter(20000);

        //        var pushTask = pipe.PushItemAsync<bool>(item, waiter, tokenSource.Token);
        //        pushTask.Wait();
        //        Assert.IsTrue(pushTask.IsCompleted);
        //        Assert.IsFalse(pushTask.IsCanceled);
        //        Assert.IsFalse(pushTask.IsFaulted);

        //        Assert.IsFalse(waiter.Task.IsCompleted);
        //        Assert.IsFalse(waiter.Task.IsCanceled);
        //        Assert.IsFalse(waiter.Task.IsFaulted);

        //        tokenSourcePop.CancelAfter(5000);

        //        var dataTask = Task.Run<DataStruct>(async () => { return await pipe.DequeueItemAsync(tokenSourcePop.Token); });
        //        dataTask.Wait();
        //        waiter.Task.Wait();
        //        Assert.IsFalse(dataTask.IsCanceled);
        //        Assert.IsFalse(dataTask.IsFaulted);
        //        Assert.IsTrue(dataTask.IsCompleted);

        //        Assert.IsFalse(pushTask.IsCanceled);
        //        Assert.IsFalse(pushTask.IsFaulted);
        //        Assert.IsTrue(pushTask.IsCompleted);

        //        var dataResult = dataTask.Result;
        //        Assert.AreEqual(item, dataResult);
        //        Assert.AreEqual(item.Data, dataResult.Data);
        //        Assert.AreEqual(key, dataResult.Data);

        //        Assert.IsTrue(waiter.Task.IsCompleted);
        //        Assert.IsTrue((bool)waiter.Task.Result);
        //    }
        //}

        ///// <summary>
        ///// Test that We can push and pop with a cancel of the push.
        ///// </summary>
        //public void PipeSource_PushAndPop_WithCancel(ITargetDescriptor init)
        //{
        //    using (T pipe = new T())
        //    {
        //        pipe.InitializeAsync(init, PipeType.TwoWay).Wait();

        //        string key = Guid.NewGuid().ToString();
        //        DataStruct item = new DataStruct();
        //        item.Data = key;

        //        CancellationTokenSource tokenSource = new CancellationTokenSource();
        //        CancellationTokenSource tokenSourcePop = new CancellationTokenSource();

        //        TaskCompletionSource<object> waiter = new TaskCompletionSource<object>();
        //        waiter.AttachCancelToken(tokenSource.Token);

        //        var pushTask = pipe.PushItemAsync<bool>(item, waiter, tokenSource.Token);
        //        pushTask.Wait();

        //        Assert.IsTrue(pushTask.IsCompleted);
        //        Assert.IsFalse(pushTask.IsCanceled);
        //        Assert.IsFalse(pushTask.IsFaulted);

        //        tokenSource.CancelAfter(1000);

        //        try
        //        {
        //            waiter.Task.Wait();
        //            Assert.Fail("Task must have been cancel");
        //        }
        //        catch (AggregateException ex)
        //        {
        //            Assert.IsTrue(ex.InnerException != null && (ex.InnerException is OperationCanceledException));
        //        }

        //        var dataTask = pipe.DequeueItemAsync(tokenSourcePop.Token);
        //        dataTask.Wait();
        //        Assert.IsFalse(dataTask.IsCanceled);
        //        Assert.IsFalse(dataTask.IsFaulted);
        //        Assert.IsTrue(dataTask.IsCompleted);

        //        Assert.IsTrue(waiter.Task.IsCanceled);
        //        Assert.IsFalse(waiter.Task.IsFaulted);

        //        var dataResult = dataTask.Result;
        //        Assert.AreEqual(item, dataResult);
        //        Assert.AreEqual(item.Data, dataResult.Data);
        //        Assert.AreEqual(key, dataResult.Data);

        //        tokenSourcePop.CancelAfter(2000);
        //        var dataTaskCancel = pipe.DequeueItemAsync(tokenSourcePop.Token);

        //        dataTaskCancel.Wait();
        //        Assert.IsNull(dataTaskCancel.Result);
        //    }
        //}

        /// <summary>
        /// Pipes the source_ send and pop.
        /// </summary>
        public void PipeSource_SendAndPop(ITargetDescriptor init)
        {
            using (T pipe = new T())
            {
                pipe.InitializeAsync(init, PipeType.TwoWay).Wait();

                string key = Guid.NewGuid().ToString();
                DataStruct item = new DataStruct();
                item.Data = key;

                var task = pipe.SendItemAsync(item);
                task.Wait();

                Assert.IsFalse(task.IsCanceled);
                Assert.IsFalse(task.IsFaulted);
                Assert.IsTrue(task.IsCompleted);

                CancellationTokenSource tokenSourcePop = new CancellationTokenSource();

                tokenSourcePop.CancelAfter(5000);

                var dataTask = pipe.DequeueItemAsync(tokenSourcePop.Token);
                dataTask.Wait();

                Assert.IsFalse(dataTask.IsCanceled);
                Assert.IsFalse(dataTask.IsFaulted);
                Assert.IsTrue(dataTask.IsCompleted);

                var dataResult = dataTask.Result;
                Assert.IsNotNull(dataResult);
                Assert.AreEqual(item, dataResult);
                Assert.AreEqual(item.Data, dataResult.Data);
                Assert.AreEqual(key, dataResult.Data);
            }
        }
        #endregion
    }
}
